僵尸进程与孤儿进程

  1. 僵尸进程
    1. 处理僵尸进程
  2. 孤儿进程
    1. 处理孤儿进程
      1. Linux
      2. Docker
      3. K8s
  3. 使用Golang的协程实现子进程的回收
  4. 附录

僵尸进程

当一个进程退出的时候,Linux操作系统会将该进程的重型资源释放(释放CPU、释放内存,关闭打开的文件等),但是会保留该进程的一部分信息(PID、终止状态、使用的CPU时间等),因此父进程在调用 wait() 时可以得到这些信息。然而如果父进程没有调用 wait() 方法获取这个子进程的结束状态的话,那么这个子进程的状态将变为Z(zombie),一般称之僵尸进程。另外,任意子进程在退出的时候还会向父进程发送一个 SIGCHLD 信号,父进程调用 wait() 方法的时候实际就是在监听此信号,来判断是否有子进程退出。

处理僵尸进程

  1. 通过signal(SIGCHLD, SIG_IGN)通知内核对子进程的结束不关心,由内核回收。如果不想让父进程挂起,可以在父进程中加入一条语句:signal(SIGCHLD,SIG_IGN);表示父进程忽略SIGCHLD信号,该信号是子进程退出的时候向父进程发送的。
  2. 父进程调用wait/waitpid等函数等待子进程结束,如果尚无子进程退出wait会导致父进程阻塞。另外,waitpid可以通过传递 WNOHANG 使父进程不阻塞立即返回。
  3. 如果父进程很忙可以用signal注册信号处理函数,在信号处理函数调用wait/waitpid等待子进程退出。
  4. 通过两次调用fork。父进程首先调用fork创建一个子进程然后waitpid等待子进程退出,子进程再fork一个孙进程后退出。这样子进程退出后会被父进程等待回收,而对于孙子进程其父进程已经退出所以孙进程成为一个孤儿进程,孤儿进程由init进程接管,孙进程结束后,init会等待回收。

孤儿进程

如果当前进程的父进程先于当前进程退出,那么当前进程的PPID会被重新设置为1(这也是1号进程被称为孤儿院的原因),此时当前进程成为孤儿进程。

处理孤儿进程

Linux

在Linux系统中1号进程为init进程。init进程有自动清理僵尸进程的功能,换句话说init进程会自动wait所有的僵尸进程,因此我们通常看不到孤儿进程的存在。

Docker

在Docker run命令中中如果开启 --init 开关的话1号进程也是init进程(此特性的后端实现是tini),然后再fork出来用户指定的进程,那么这个容器中所有的僵尸进程都可以放心的交给init进程来回收。否则1号进程为用户指定的进程,需要由用户指定的进程实现僵尸进程回收。

K8s

K8s上没有给出像docker一样简便的方式自动回收孤儿进程,一种方式是通过显示的方式启动tini,另一种方式就是在一号进程中实现孤儿进程的回收逻辑。

使用Golang的协程实现子进程的回收

func ReapChildren(ctx context.Context) {
    c := make(chan os.Signal, 1)
    signal.Notify(c, unix.SIGCHLD)

    for {
        select {
        case <-c:
        case <-ctx.Done():
            fmt.Println("reaper exit")
            return
        }

        fmt.Println("Received signal SIGCHLD")

        func() {
        POLL:
            var status unix.WaitStatus
            pid, err := unix.Wait4(-1, &status, unix.WNOHANG, nil)
            switch err {
            case nil:
                if pid > 0 {
                    fmt.Println("cleanup child pid=%v, status=%v, exit_status=%v", pid, status, status.ExitStatus())
                    goto POLL
                }
                return

            case unix.ECHILD:
                return

            case unix.EINTR:
                goto POLL

            default:
                fmt.Println("unknown error: %v", err)
                return
            }
        }()
    }
}

附录


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 nz_nuaa@163.com
github